package com.huanzhidadi.ratelimiter.limiter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

/**
 * 漏桶限流法
 * 持续速率和瞬时最大速率都是漏出的速率
 */
@Slf4j
@Component
@RefreshScope
public class LeakyBucketRateLimiter implements RateLimiter {
    // 桶的容量
    @Value("${limiter.leakyBucket.capacity}")
    private Integer capacity = 50;
    // 漏出的速率
    @Value("${limiter.leakyBucket.permitsPerSecond}")
    private Integer permitsPerSecond = 10;
    // 剩余水量
    private long leftWater;
    // 上次注入水的时间
    private long timeStamp = System.currentTimeMillis();

    public LeakyBucketRateLimiter() {}

    public LeakyBucketRateLimiter(Integer capacity, Integer permitsPerSecond) {
        this.capacity = capacity;
        this.permitsPerSecond = permitsPerSecond;
    }

    /**
     * 这个方法和令牌桶的区别主要是漏桶漏水的速率才是限流的主要作用
     * 比如说1s中允许一个线程来访问这个接口，也就是说1s中只能处理一个请求
     * 此时流出水就是一秒钟一下
     * 此时有请求过来了就会在当前漏桶中+1这样直到漏桶满了就拒绝访问了
     * 栗子：
     * 假如1秒钟10个请求
     * timeGap = (now - timeStamp) / 1000; 此时timeGap = 1 也就是当前已经过去一秒钟了
     * 假设leftWater当前为6也就是当前桶中有6个水滴
     * leftWater = Math.max(0, leftWater - timeGap * permitsPerSecond) 就是取0和6-1*2=4中最大的
     * 所以leftWater = 4
     * 也就是说当前桶中容量变为4了
     * 此时判断leftWater < capacity（10） 此时 可以继续往里面增加请求也就是可以到达10个请求
     * 如果当前leftWater >= capacity（10）拒绝就好了
     */
    @Override
    public synchronized boolean tryAcquire() {
        log.info("capacity:" + capacity);
        log.info("permitsPerSecond:" + permitsPerSecond);
        // 计算剩余水量
        long now = System.currentTimeMillis();
        //1000ms多少个请求
        long timeGap = (now - timeStamp) / 1000;
        leftWater = Math.max(0, leftWater - timeGap * permitsPerSecond);
        if (timeGap > 0) {
            log.info("timeGap: " + timeGap);
            timeStamp = now;
        }
        // 如果未满，则放行；否则限流
        log.info("leftWater: " + leftWater);
        if (leftWater < capacity){
            leftWater += 1;
            return true;
        }
        return false;
    }

}